package com.uye.framework.controller;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.uye.framework.command.BaseStatefulJSONCommand;
import com.uye.framework.command.ReturnErrorCommand;
import com.uye.framework.command.ServiceCommandFactory;
import com.uye.framework.exception.BadRequestException;
import com.uye.framework.exception.InternalServerException;
import com.uye.framework.util.StringUtils;

/**
 * Base controller responsible for invoking the appropriate command for the
 * appropriate service name.
 * 
 * @author uye
 * 
 */
@Controller
public class BaseJSONServiceController {

	private Logger logger = Logger.getLogger(this.getClass());
	private final static String SERVICE_NAME = "serviceName";
	private final static String JSON_REQUEST_PARAM_NAME = "request";
	private ReturnErrorCommand returnErrorCommand;

	/**
	 * Commands associated with this controller. Subclasses or other instances
	 * can have their own separate factory to control which commands they can
	 * execute.
	 */
	@Autowired
	private ServiceCommandFactory publicServiceFactory;

	/**
	 * Generic JSON service that accepts and processes requests not requiring
	 * authorization nor authentication. Main entry point for client
	 * applications. Subclasses can define their own service-command mapping and
	 * implement security.
	 * 
	 * @param httpServletRequest
	 *            The original HTTP request to access request context
	 * @param httpServletResponse
	 *            The response object JSON service commands will be using to
	 *            reply back to the calling client
	 * 
	 */
	@SuppressWarnings("unchecked")
	@RequestMapping("/public.service")
	public void doService(final HttpServletRequest httpServletRequest,
			final HttpServletResponse httpServletResponse) {

		String serviceName = "unknown";
		String jsonString = "";
		Map<String, String> mappedJSONString;

		try {

			jsonString = extractJsonString(httpServletRequest, serviceName);
			mappedJSONString = mapJSONString(serviceName, jsonString);
			serviceName = retrieveServiceName(jsonString, mappedJSONString);

			if (null == publicServiceFactory) {
				throw new InternalServerException(serviceName,
						"A command factory has not been "
								+ "assigned to the responding controller.");
			}
			
			logger.debug("Getting a command for service request: "+ serviceName);
			
			BaseStatefulJSONCommand service = publicServiceFactory
					.getCommandFromJSONString(jsonString);
			
			service.initialize(httpServletRequest, httpServletResponse);
			service.execute();

		} catch (InternalServerException ise) {
			ise.setServiceName(serviceName);
			handleInternalServerException(httpServletResponse, ise);
		} catch (Exception e) {
			InternalServerException ise = new InternalServerException(e);
			ise.setServiceName(serviceName);
			handleInternalServerException(httpServletResponse, ise);
		}
	}

	private void handleInternalServerException(
			final HttpServletResponse httpServletResponse,
			InternalServerException ise) {

		// if not set, create a new instance
		if (null == returnErrorCommand) {
			returnErrorCommand = new ReturnErrorCommand();
		}

		returnErrorCommand.initialize(httpServletResponse, ise);

		try {
			returnErrorCommand.execute();
		} catch (Exception e) {
			logger.error(StringUtils.toString(e));
		}
	}

	private String retrieveServiceName(String jsonString,
			Map<String, String> mappedJSONString) throws BadRequestException {
		String serviceName;
		serviceName = mappedJSONString.get(SERVICE_NAME);
		if (null == serviceName) {
			throw new BadRequestException(serviceName,
					"Could not retrieve the serviceName from "
							+ "the request: \n" + jsonString + "\n");
		}
		return serviceName;
	}

	@SuppressWarnings("unchecked")
	private Map<String, String> mapJSONString(String serviceName,
			String jsonString) throws BadRequestException {
		Map<String, String> mappedJSONString;
		try {
			mappedJSONString = (HashMap<String, String>) (JSONObject.toBean(
					JSONObject.fromObject(jsonString), HashMap.class));
		} catch (JSONException e) {
			throw new BadRequestException(serviceName,
					"Could not parse the passed JSON string: \n" + jsonString
							+ "\n");
		}
		return mappedJSONString;
	}

	private String extractJsonString(
			final HttpServletRequest httpServletRequest, String serviceName)
			throws BadRequestException {
		String jsonString;
		jsonString = httpServletRequest.getParameter(JSON_REQUEST_PARAM_NAME);
		if (null == jsonString) {
			throw new BadRequestException(serviceName,
					"Could not retrieve parameter: " + JSON_REQUEST_PARAM_NAME
							+ " from request");
		}
		return jsonString;
	}

	/**
	 * Sets the factory responsible for instantiating services.
	 * 
	 * @param commandFactory
	 *            The new CommandFactory
	 */
	public void setServiceFactory(final ServiceCommandFactory commandFactory) {
		this.publicServiceFactory = commandFactory;
	}

	/**
	 * Retrieves the CommandFactory associated with the controller.
	 * 
	 * @return The associated CommandFactory
	 */
	public ServiceCommandFactory getServiceFactory() {
		return publicServiceFactory;
	}

	public void setReturnErrorCommand(ReturnErrorCommand returnErrorCommand) {
		this.returnErrorCommand = returnErrorCommand;
	}

	public ReturnErrorCommand getReturnErrorCommand() {
		return returnErrorCommand;
	}

}
